package de.dhbw.karlsruhe.picsim.model.cpu.impl;

import de.dhbw.karlsruhe.picsim.model.commands.PicInstruction;
import de.dhbw.karlsruhe.picsim.model.commands.impl.Call;
import de.dhbw.karlsruhe.picsim.model.commands.impl.NoOperation;
import de.dhbw.karlsruhe.picsim.model.cpu.FileMutable;
import de.dhbw.karlsruhe.picsim.model.cpu.InstructionPipeline;
import de.dhbw.karlsruhe.picsim.model.cpu.Interrupt;
import de.dhbw.karlsruhe.picsim.model.cpu.ProgramCounterMutable;
import de.dhbw.karlsruhe.picsim.model.decode.Decoder;
import de.dhbw.karlsruhe.picsim.model.memory.ProgramMemory;
import de.dhbw.karlsruhe.picsim.model.memory.impl.RegisterValueImpl;
import de.dhbw.karlsruhe.picsim.util.Bits;
import de.dhbw.karlsruhe.picsim.util.BitsMutable;
import de.dhbw.karlsruhe.picsim.util.impl.BitUtilities;
import de.dhbw.karlsruhe.picsim.util.impl.BitsMutableImpl;
import de.dhbw.karlsruhe.picsim.util.impl.Lengths;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by IntelliJ IDEA.
 * User: goesen
 * Date: Apr 21, 2010
 * Time: 12:33:38 PM
 */
public final class InstructionPipelineImpl implements InstructionPipeline {
    private static final Log log = LogFactory.getLog(InstructionPipelineImpl.class);
    private final AtomicBoolean hasExtraNoOperation;
    private final Decoder decoder;
    private final ProgramMemory programMemory;
    private final ProgramCounterMutable programCounter;
    private final FileMutable intCon;

    public InstructionPipelineImpl(final Decoder decoder,
                                   final ProgramMemory programMemory,
                                   final ProgramCounterMutable counterMutable, final FileMutable intCon) {
        this.decoder = decoder;
        this.programMemory = programMemory;
        programCounter = counterMutable;
        this.intCon = intCon;
        hasExtraNoOperation = new AtomicBoolean(false);
    }

    @Override
    public void addExtraNoOp() {
        hasExtraNoOperation.set(true);
    }

    @NotNull
    @Override
    public PicInstruction popCommand() {
        if (hasExtraNoOperation.compareAndSet(true, false)) {
            log.debug("next command will be a noOp because of previous Programm Counter modification");
            return new NoOperation();
        }


        final Interrupt[] allPossibleInterrupts = Interrupt.values();
        final Bits intConBits = intCon.getData();
        for (final Interrupt interrupt : allPossibleInterrupts) {
            if (interruptIsActive(intConBits, interrupt)) {
                log.debug("next command will be call04 because of interrupt");
                // lösche das Global Interrupts enabled Flag
                final BitsMutable bitsMutable = new BitsMutableImpl(intConBits);
                bitsMutable.setBit(7, false);
                intCon.setValue(new RegisterValueImpl(bitsMutable));
                //und führe den Call durch
                return new Call(BitUtilities.asLeastSignificantBitFirst(4, Lengths.CALL_GOTO_ARGUMENT_LENGTH));
            }
        }
        log.debug("next command will be an command from programMemory");
        final PicInstruction instruction = decoder.decode(programMemory.getOpCodeAt(programCounter.getCurrentValue()));
        programCounter.increment();
        return instruction;
    }

    private static boolean interruptIsActive(final Bits intConBits, final Interrupt interrupt) {
        return isGlobalInterruptEnabled(intConBits) && intConBits.isSet(interrupt.enablePosition()) &&
                intConBits.isSet(interrupt.getFlagPosition());
    }

    private static boolean isGlobalInterruptEnabled(final Bits intConBits) {
        return intConBits.isSet(7);
    }
}

